package com.kaede.hayate.security.jwt;

import com.kaede.hayate.common.util.CommonUtil;
import com.kaede.hayate.common.util.JsonUtils;
import com.kaede.hayate.config.ApplicationProperties;
import com.kaede.hayate.security.entity.UserInfo;
import io.jsonwebtoken.*;
import org.apache.tomcat.util.codec.binary.Base64;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class JwtService {

    private final Logger log = LoggerFactory.getLogger(JwtService.class);

    public static final String USER_ID_KEY = "userId";
    public static final String USERNAME_KEY = "username";
    public static final String REMEMBER_KEY = "remember";

    @Autowired
    private ApplicationProperties applicationProperties;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private UserDetailsService userDetailsService;

    public String createToken(String userId, String username, Boolean remember, String tokenSecret) {
        return Jwts.builder()
                .claim(USER_ID_KEY, userId)
                .claim(USERNAME_KEY, username)
                .claim(REMEMBER_KEY, remember == null ? false : remember)
                .signWith(SignatureAlgorithm.HS512, tokenSecret)
                .compact();
    }

    public boolean validateToken(String authToken, String tokenSecret) {
        try {
            Jwts.parser().setSigningKey(tokenSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            log.info("Invalid JWT signature.");
            log.trace("Invalid JWT signature trace: {}", e);
        } catch (MalformedJwtException e) {
            log.info("Invalid JWT token.");
            log.trace("Invalid JWT token trace: {}", e);
        } catch (ExpiredJwtException e) {
            log.info("Expired JWT token.");
            log.trace("Expired JWT token trace: {}", e);
        } catch (UnsupportedJwtException e) {
            log.info("Unsupported JWT token.");
            log.trace("Unsupported JWT token trace: {}", e);
        } catch (IllegalArgumentException e) {
            log.info("JWT token compact of handler are invalid.");
            log.trace("JWT token compact of handler are invalid trace: {}", e);
        }
        return false;
    }

    public UserInfo getUnsignedUserInfoFromToken(String jwt) {
        UserInfo userInfo = new UserInfo();
        String[] array = jwt.split("\\.");
        if(array.length == 3){
            Map<String,Object> claims = JsonUtils.jsonToPojo(Base64.decodeBase64(array[1]), Map.class);
            if(claims.containsKey(USER_ID_KEY) && claims.containsKey(USERNAME_KEY) && claims.containsKey(REMEMBER_KEY)) {
                userInfo.setUserId(claims.get(USER_ID_KEY).toString());
                userInfo.setUsername(claims.get(USERNAME_KEY).toString());
                userInfo.setRemember((Boolean) claims.get(REMEMBER_KEY));
                return userInfo;
            }
        }
        return null;
    }

    public UserDetails getUserDetails(String jwt, HttpServletRequest request){
        //检查是否是忽略token的url
        boolean isIgnoreUrl = false;
        for (String pattern : applicationProperties.getIgnoreUrls()){
            if(new AntPathRequestMatcher(pattern).matches(request)){
                isIgnoreUrl = true;
            }
        }
        //如果不是忽略token的url
        if(!isIgnoreUrl){
            UserInfo userInfo = getUnsignedUserInfoFromToken(jwt);
            //如果token能解析出用户信息
            if(userInfo != null) {
                BoundValueOperations<String, String> valueOperations = stringRedisTemplate.boundValueOps(applicationProperties.getTokenSecretPrefix() + userInfo.getUsername());
                String tokenSecret = valueOperations.get();
                //如果secret不为空并且有效
                if (!StringUtils.isEmpty(tokenSecret) && validateToken(jwt, tokenSecret)) {

                    //更新secret时效
                    if (userInfo.getRemember()) {
                        valueOperations.expire(applicationProperties.getTokenValidityInSecondsForRememberMe(), TimeUnit.SECONDS);
                    } else {
                        valueOperations.expire(applicationProperties.getTokenValidityInSeconds(), TimeUnit.SECONDS);
                    }
                    return userDetailsService.loadUserByUsername(userInfo.getUsername());
                }else {
                    request.setAttribute(applicationProperties.getTokenInvalidName(), true);
                }
            }
        }
        return null;
    }

    public String getSignedTokenSecret(String tokenSecret){
        StringBuffer sb = new StringBuffer();
        sb.append(tokenSecret).append(new DateTime().toString("yyyy-MM-dd HH:mm:ss")).append(CommonUtil.CreateNoncestr(4));
        return sb.toString();
    }
}
